/* This class has been written by
 * Corinna John (Hannover, Germany)
 * cj@binary-universe.net
 * 
 * You may do with this code whatever you like,
 * except selling it or claiming any rights/ownership.
 * 
 * Please send me a little feedback about what you're
 * using this code for and what changes you'd like to
 * see in later versions. (And please excuse my bad english.)
 * 
 * WARNING: This is experimental code.
 * Please do not expect "Release Quality".
 * */

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;

namespace AviFile 
{

	public class Avi{

		public static int PALETTE_SIZE = 4*256; //RGBQUAD * 256 colours

		public static readonly int streamtypeVIDEO = mmioFOURCC('v', 'i', 'd', 's');
		public static readonly int streamtypeAUDIO = mmioFOURCC('a', 'u', 'd', 's');
		public static readonly int streamtypeMIDI = mmioFOURCC('m', 'i', 'd', 's');
		public static readonly int streamtypeTEXT = mmioFOURCC('t', 'x', 't', 's');
		
		public const int OF_SHARE_DENY_WRITE = 32;
		public const int OF_WRITE			 = 1;
		public const int OF_READWRITE		 = 2;
		public const int OF_CREATE			 = 4096;

		public const int BMP_MAGIC_COOKIE = 19778; //ascii string "BM"

		public const int AVICOMPRESSF_INTERLEAVE = 0x00000001;    // interleave
		public const int AVICOMPRESSF_DATARATE = 0x00000002;    // use a data rate
		public const int AVICOMPRESSF_KEYFRAMES = 0x00000004;    // use keyframes
		public const int AVICOMPRESSF_VALID = 0x00000008;    // has valid data
		public const int AVIIF_KEYFRAME = 0x00000010;

		public const UInt32 ICMF_CHOOSE_KEYFRAME = 0x0001;	// show KeyFrame Every box
		public const UInt32 ICMF_CHOOSE_DATARATE = 0x0002;	// show DataRate box
		public const UInt32 ICMF_CHOOSE_PREVIEW  = 0x0004;	// allow expanded preview dialog

        //macro mmioFOURCC
        public static Int32 mmioFOURCC(char ch0, char ch1, char ch2, char ch3) {
            return ((Int32)(byte)(ch0) | ((byte)(ch1) << 8) |
                ((byte)(ch2) << 16) | ((byte)(ch3) << 24));
        }

		#region structure declarations

		[StructLayout(LayoutKind.Sequential, Pack=1)]
		public struct RECT{ 
			public UInt32 left; 
			public UInt32 top; 
			public UInt32 right; 
			public UInt32 bottom; 
		} 		

		[StructLayout(LayoutKind.Sequential, Pack=1)]
		public struct BITMAPINFOHEADER {
			public Int32 biSize;
			public Int32 biWidth;
			public Int32 biHeight;
			public Int16 biPlanes;
			public Int16 biBitCount;
			public Int32 biCompression;
			public Int32 biSizeImage;
			public Int32 biXPelsPerMeter;
			public Int32 biYPelsPerMeter;
			public Int32 biClrUsed;
			public Int32 biClrImportant;
		}

		[StructLayout(LayoutKind.Sequential)] 
		public struct PCMWAVEFORMAT {
			public short wFormatTag;
			public short nChannels;
			public int nSamplesPerSec;
			public int nAvgBytesPerSec;
			public short nBlockAlign;
			public short wBitsPerSample;
			public short cbSize;
		}

		[StructLayout(LayoutKind.Sequential, Pack=1)]
		public struct AVISTREAMINFO {
			public Int32    fccType;
			public Int32    fccHandler;
			public Int32    dwFlags;
			public Int32    dwCaps;
			public Int16    wPriority;
			public Int16    wLanguage;
			public Int32    dwScale;
			public Int32    dwRate;
			public Int32    dwStart;
			public Int32    dwLength;
			public Int32    dwInitialFrames;
			public Int32    dwSuggestedBufferSize;
			public Int32    dwQuality;
			public Int32    dwSampleSize;
			public RECT		rcFrame;
			public Int32    dwEditCount;
			public Int32    dwFormatChangeCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst=64)]
			public UInt16[]    szName;
		}
		[StructLayout(LayoutKind.Sequential, Pack=1)]
		public struct BITMAPFILEHEADER{
			public Int16 bfType; //"magic cookie" - must be "BM"
			public Int32 bfSize;
			public Int16 bfReserved1;
			public Int16 bfReserved2;
			public Int32 bfOffBits;
		}

				
		[StructLayout(LayoutKind.Sequential, Pack=1)]
			public struct AVIFILEINFO{
			public Int32 dwMaxBytesPerSecond;
			public Int32 dwFlags;
			public Int32 dwCaps;
			public Int32 dwStreams;
			public Int32 dwSuggestedBufferSize;
			public Int32 dwWidth;
			public Int32 dwHeight;
			public Int32 dwScale;
			public Int32 dwRate;
			public Int32 dwLength;
			public Int32 dwEditCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst=64)]
			public char[] szFileType;
		}

		[StructLayout(LayoutKind.Sequential, Pack=1)]
			public struct AVICOMPRESSOPTIONS {
			public UInt32   fccType;
			public UInt32   fccHandler;
			public UInt32   dwKeyFrameEvery;  // only used with AVICOMRPESSF_KEYFRAMES
			public UInt32   dwQuality;
			public UInt32   dwBytesPerSecond; // only used with AVICOMPRESSF_DATARATE
			public UInt32   dwFlags;
			public IntPtr   lpFormat;
			public UInt32   cbFormat;
			public IntPtr   lpParms;
			public UInt32   cbParms;
			public UInt32   dwInterleaveEvery;
		}

		/// <summary>AviSaveV needs a pointer to a pointer to an AVICOMPRESSOPTIONS structure</summary>
		[StructLayout(LayoutKind.Sequential, Pack=1)]
			public class AVICOMPRESSOPTIONS_CLASS {
			public UInt32   fccType;
			public UInt32   fccHandler;
			public UInt32   dwKeyFrameEvery;  // only used with AVICOMRPESSF_KEYFRAMES
			public UInt32   dwQuality;
			public UInt32   dwBytesPerSecond; // only used with AVICOMPRESSF_DATARATE
			public UInt32   dwFlags;
			public IntPtr   lpFormat;
			public UInt32   cbFormat;
			public IntPtr   lpParms;
			public UInt32   cbParms;
			public UInt32   dwInterleaveEvery;

			public AVICOMPRESSOPTIONS ToStruct(){
				AVICOMPRESSOPTIONS returnVar = new AVICOMPRESSOPTIONS();
				returnVar.fccType = this.fccType;
				returnVar.fccHandler = this.fccHandler;
				returnVar.dwKeyFrameEvery = this.dwKeyFrameEvery;
				returnVar.dwQuality = this.dwQuality;
				returnVar.dwBytesPerSecond = this.dwBytesPerSecond;
				returnVar.dwFlags = this.dwFlags;
				returnVar.lpFormat = this.lpFormat;
				returnVar.cbFormat = this.cbFormat;
				returnVar.lpParms = this.lpParms;
				returnVar.cbParms = this.cbParms;
				returnVar.dwInterleaveEvery = this.dwInterleaveEvery;
				return returnVar;
			}
		}
		#endregion structure declarations

		#region method declarations
	
		//Initialize the AVI library
		[DllImport("avifil32.dll")]
		public static extern void AVIFileInit();

		//Open an AVI file
		[DllImport("avifil32.dll", PreserveSig=true)]
		public static extern int AVIFileOpen(
			ref int ppfile,
			String szFile,
			int uMode,
			int pclsidHandler);

		//Get a stream from an open AVI file
		[DllImport("avifil32.dll")]
		public static extern int AVIFileGetStream(
			int pfile,
			out IntPtr ppavi,  
			int fccType,       
			int lParam);

		//Get the start position of a stream
		[DllImport("avifil32.dll", PreserveSig=true)]
		public static extern int AVIStreamStart(int pavi);

		//Get the length of a stream in frames
		[DllImport("avifil32.dll", PreserveSig=true)]
		public static extern int AVIStreamLength(int pavi);

		//Get information about an open stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamInfo(
			IntPtr pAVIStream,
			ref AVISTREAMINFO psi,
			int lSize);

		//Get a pointer to a GETFRAME object (returns 0 on error)
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamGetFrameOpen(
			IntPtr pAVIStream,
			ref BITMAPINFOHEADER bih);

		//Get a pointer to a packed DIB (returns 0 on error)
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamGetFrame(
			int pGetFrameObj,
			int lPos);

		//Create a new stream in an open AVI file
		[DllImport("avifil32.dll")]
		public static extern int AVIFileCreateStream(
			int pfile,
			out IntPtr ppavi, 
			ref AVISTREAMINFO ptr_streaminfo);

        //Create an editable stream
        [DllImport("avifil32.dll")]
        public static extern int CreateEditableStream(
            ref IntPtr ppsEditable,
            IntPtr psSource
        );

        //Cut samples from an editable stream
        [DllImport("avifil32.dll")]
        public static extern int EditStreamCut(
            IntPtr pStream,
            ref Int32 plStart,
            ref Int32 plLength,
            ref IntPtr ppResult
        );

        //Copy a part of an editable stream
        [DllImport("avifil32.dll")]
        public static extern int EditStreamCopy(
            IntPtr pStream,
            ref Int32 plStart,
            ref Int32 plLength,
            ref IntPtr ppResult
        );

        //Paste an editable stream into another editable stream
        [DllImport("avifil32.dll")]
        public static extern int EditStreamPaste(
            IntPtr pStream,
            ref Int32 plPos,
            ref Int32 plLength,
            IntPtr pstream,
            Int32 lStart,
            Int32 lLength
        );

        //Change a stream's header values
        [DllImport("avifil32.dll")]
        public static extern int EditStreamSetInfo(
            IntPtr pStream,
            ref AVISTREAMINFO lpInfo,
            Int32 cbInfo
        );

        [DllImport("avifil32.dll")]
        public static extern int AVIMakeFileFromStreams(
            ref IntPtr ppfile,
            int nStreams,
            ref IntPtr papStreams
        );

        //Set the format for a new stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamSetFormat(
			IntPtr aviStream, Int32 lPos, 
			ref BITMAPINFOHEADER lpFormat, Int32 cbFormat);
		
		//Set the format for a new stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamSetFormat(
			IntPtr aviStream, Int32 lPos, 
			ref PCMWAVEFORMAT lpFormat, Int32 cbFormat);
		
		//Read the format for a stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamReadFormat(
			IntPtr aviStream, Int32 lPos,
			ref BITMAPINFOHEADER lpFormat, ref Int32 cbFormat
			);

		//Read the size of the format for a stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamReadFormat(
			IntPtr aviStream, Int32 lPos,
			int empty, ref Int32 cbFormat
			);
		
		//Read the format for a stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamReadFormat(
			IntPtr aviStream, Int32 lPos,
			ref PCMWAVEFORMAT lpFormat, ref Int32 cbFormat
			);

		//Write a sample to a stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamWrite(
			IntPtr aviStream, Int32 lStart, Int32 lSamples, 
			IntPtr lpBuffer, Int32 cbBuffer, Int32 dwFlags, 
			Int32 dummy1, Int32 dummy2);

		//Release the GETFRAME object
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamGetFrameClose(
			int pGetFrameObj);

		//Release an open AVI stream
		[DllImport("avifil32.dll")]
		public static extern int AVIStreamRelease(IntPtr aviStream);

		//Release an open AVI file
		[DllImport("avifil32.dll")]
		public static extern int AVIFileRelease(int pfile);

		//Close the AVI library
		[DllImport("avifil32.dll")]
		public static extern void AVIFileExit();

		[DllImport("avifil32.dll")]
		public static extern int AVIMakeCompressedStream(
			out IntPtr ppsCompressed, IntPtr aviStream, 
			ref AVICOMPRESSOPTIONS ao, int dummy);

		[DllImport("avifil32.dll")]
		public static extern bool AVISaveOptions(
			IntPtr hwnd,
			UInt32 uiFlags,              
			Int32 nStreams,                      
			ref IntPtr ppavi,
			ref AVICOMPRESSOPTIONS_CLASS plpOptions  
			);

		[DllImport("avifil32.dll")]
		public static extern long AVISaveOptionsFree(
			int nStreams,
			ref AVICOMPRESSOPTIONS_CLASS plpOptions  
			);

		[DllImport("avifil32.dll")]
		public static extern int AVIFileInfo(
			int pfile, 
			ref AVIFILEINFO pfi,
			int lSize);

		[DllImport("winmm.dll", EntryPoint="mmioStringToFOURCCA")]
		public static extern int mmioStringToFOURCC(String sz, int uFlags);

		[DllImport("avifil32.dll")]
		public static extern int AVIStreamRead(
			IntPtr pavi, 
			Int32 lStart,     
			Int32 lSamples,   
			IntPtr lpBuffer, 
			Int32 cbBuffer,   
			Int32  plBytes,  
			Int32  plSamples 
			);

		[DllImport("avifil32.dll")]
		public static extern int AVISaveV(
			String szFile,
			Int16 empty,
			Int16 lpfnCallback,
			Int16 nStreams,
			ref IntPtr ppavi,
			ref AVICOMPRESSOPTIONS_CLASS plpOptions
			);

		#endregion method declarations

	}

    public class AudioStream : AviStream
    {

        public int CountBitsPerSample
        {
            get { return waveFormat.wBitsPerSample; }
        }

        public int CountSamplesPerSecond
        {
            get { return waveFormat.nSamplesPerSec; }
        }

        public int CountChannels
        {
            get { return waveFormat.nChannels; }
        }

        /// <summary>the stream's format</summary>
        private Avi.PCMWAVEFORMAT waveFormat = new Avi.PCMWAVEFORMAT();

        /// <summary>Initialize an AudioStream for an existing stream</summary>
        /// <param name="aviFile">The file that contains the stream</param>
        /// <param name="aviStream">An IAVISTREAM from [aviFile]</param>
        public AudioStream(int aviFile, IntPtr aviStream)
        {
            this.aviFile = aviFile;
            this.aviStream = aviStream;

            int size = Marshal.SizeOf(waveFormat);
            Avi.AVIStreamReadFormat(aviStream, 0, ref waveFormat, ref size);
            Avi.AVISTREAMINFO streamInfo = GetStreamInfo(aviStream);
        }

        /// <summary>Read the stream's header information</summary>
        /// <param name="aviStream">The IAVISTREAM to read from</param>
        /// <returns>AVISTREAMINFO</returns>
        private Avi.AVISTREAMINFO GetStreamInfo(IntPtr aviStream)
        {
            Avi.AVISTREAMINFO streamInfo = new Avi.AVISTREAMINFO();
            int result = Avi.AVIStreamInfo(aviStream, ref streamInfo, Marshal.SizeOf(streamInfo));
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamInfo: " + result.ToString());
            }
            return streamInfo;
        }

        /// <summary>Read the stream's header information</summary>
        /// <returns>AVISTREAMINFO</returns>
        public Avi.AVISTREAMINFO GetStreamInfo()
        {
            if (writeCompressed)
            {
                return GetStreamInfo(compressedStream);
            }
            else
            {
                return GetStreamInfo(aviStream);
            }
        }

        /// <summary>Read the stream's format information</summary>
        /// <returns>PCMWAVEFORMAT</returns>
        public Avi.PCMWAVEFORMAT GetFormat()
        {
            Avi.PCMWAVEFORMAT format = new Avi.PCMWAVEFORMAT();
            int size = Marshal.SizeOf(format);
            int result = Avi.AVIStreamReadFormat(aviStream, 0, ref format, ref size);
            return format;
        }

        /// <summary>Returns all data needed to copy the stream</summary>
        /// <remarks>Do not forget to call Marshal.FreeHGlobal and release the raw data pointer</remarks>
        /// <param name="streamInfo">Receives the header information</param>
        /// <param name="format">Receives the format</param>
        /// <param name="streamLength">Receives the length of the stream</param>
        /// <returns>Pointer to the wave data</returns>
        public IntPtr GetStreamData(ref Avi.AVISTREAMINFO streamInfo, ref Avi.PCMWAVEFORMAT format, ref int streamLength)
        {
            streamInfo = GetStreamInfo();

            format = GetFormat();
            //length in bytes = length in samples * length of a sample
            streamLength = Avi.AVIStreamLength(aviStream.ToInt32()) * streamInfo.dwSampleSize;
            IntPtr waveData = Marshal.AllocHGlobal(streamLength);

            int result = Avi.AVIStreamRead(aviStream, 0, streamLength, waveData, streamLength, 0, 0);
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamRead: " + result.ToString());
            }

            return waveData;
        }

        /// <summary>Copy the stream into a new file</summary>
        /// <param name="fileName">Name of the new file</param>
        public override void ExportStream(String fileName)
        {
            Avi.AVICOMPRESSOPTIONS_CLASS opts = new Avi.AVICOMPRESSOPTIONS_CLASS();
            opts.fccType = (UInt32)Avi.mmioStringToFOURCC("auds", 0);
            opts.fccHandler = (UInt32)Avi.mmioStringToFOURCC("CAUD", 0);
            opts.dwKeyFrameEvery = 0;
            opts.dwQuality = 0;
            opts.dwFlags = 0;
            opts.dwBytesPerSecond = 0;
            opts.lpFormat = new IntPtr(0);
            opts.cbFormat = 0;
            opts.lpParms = new IntPtr(0);
            opts.cbParms = 0;
            opts.dwInterleaveEvery = 0;

            Avi.AVISaveV(fileName, 0, 0, 1, ref aviStream, ref opts);
        }

    }

    public class AviManager
    {
        private int aviFile = 0;
        private ArrayList streams = new ArrayList();

        /// <summary>Open or create an AVI file</summary>
        /// <param name="fileName">Name of the AVI file</param>
        /// <param name="open">true: Open the file; false: Create or overwrite the file</param>
        public AviManager(String fileName, bool open)
        {
            Avi.AVIFileInit();
            int result;

            if (open)
            { //open existing file

                result = Avi.AVIFileOpen(
                    ref aviFile, fileName,
                    Avi.OF_READWRITE, 0);

            }
            else
            { //create empty file

                result = Avi.AVIFileOpen(
                    ref aviFile, fileName,
                    Avi.OF_WRITE | Avi.OF_CREATE, 0);

            }

            if (result != 0)
            {
                throw new Exception("Exception in AVIFileOpen: " + result.ToString());
            }
        }

        private AviManager(int aviFile)
        {
            this.aviFile = aviFile;
        }

        /// <summary>Get the first video stream - usually there is only one video stream</summary>
        /// <returns>VideoStream object for the stream</returns>
        public VideoStream GetVideoStream()
        {
            IntPtr aviStream;

            int result = Avi.AVIFileGetStream(
                aviFile,
                out aviStream,
                Avi.streamtypeVIDEO, 0);

            if (result != 0)
            {
                throw new Exception("Exception in AVIFileGetStream: " + result.ToString());
            }

            VideoStream stream = new VideoStream(aviFile, aviStream);
            streams.Add(stream);
            return stream;
        }

        /// <summary>Getthe first wave audio stream</summary>
        /// <returns>AudioStream object for the stream</returns>
        public AudioStream GetWaveStream()
        {
            IntPtr aviStream;

            int result = Avi.AVIFileGetStream(
                aviFile,
                out aviStream,
                Avi.streamtypeAUDIO, 0);

            if (result != 0)
            {
                throw new Exception("Exception in AVIFileGetStream: " + result.ToString());
            }

            AudioStream stream = new AudioStream(aviFile, aviStream);
            streams.Add(stream);
            return stream;
        }

        /// <summary>Get a stream from the internal list of opened streams</summary>
        /// <param name="index">Index of the stream. The streams are not sorted, the first stream is the one that was opened first.</param>
        /// <returns>VideoStream at position [index]</returns>
        /// <remarks>
        /// Use this method after DecompressToNewFile,
        /// to get the copied stream from the new AVI file
        /// </remarks>
        /// <example>
        /// //streams cannot be edited - copy to a new file
        ///	AviManager newManager = aviStream.DecompressToNewFile(@"..\..\testdata\temp.avi", true);
        /// //there is only one stream in the new file - get it and add a frame
        ///	VideoStream aviStream = newManager.GetOpenStream(0);
        ///	aviStream.AddFrame(bitmap);
        /// </example>
        public VideoStream GetOpenStream(int index)
        {
            return (VideoStream)streams[index];
        }

        /// <summary>Add an empty video stream to the file</summary>
        /// <param name="isCompressed">true: Create a compressed stream before adding frames</param>
        /// <param name="frameRate">Frames per second</param>
        /// <param name="frameSize">Size of one frame in bytes</param>
        /// <param name="width">Width of each image</param>
        /// <param name="height">Height of each image</param>
        /// <param name="format">PixelFormat of the images</param>
        /// <returns>VideoStream object for the new stream</returns>
        public VideoStream AddVideoStream(bool isCompressed, double frameRate, int frameSize, int width, int height, PixelFormat format)
        {
            VideoStream stream = new VideoStream(aviFile, isCompressed, frameRate, frameSize, width, height, format);
            streams.Add(stream);
            return stream;
        }

        /// <summary>Add an empty video stream to the file</summary>
        /// <remarks>Compresses the stream without showing the codecs dialog</remarks>
        /// <param name="compressOptions">Compression options</param>
        /// <param name="frameRate">Frames per second</param>
        /// <param name="firstFrame">Image to write into the stream as the first frame</param>
        /// <returns>VideoStream object for the new stream</returns>
        public VideoStream AddVideoStream(Avi.AVICOMPRESSOPTIONS compressOptions, double frameRate, Bitmap firstFrame)
        {
            VideoStream stream = new VideoStream(aviFile, compressOptions, frameRate, firstFrame);
            streams.Add(stream);
            return stream;
        }

        /// <summary>Add an empty video stream to the file</summary>
        /// <param name="isCompressed">true: Create a compressed stream before adding frames</param>
        /// <param name="frameRate">Frames per second</param>
        /// <param name="firstFrame">Image to write into the stream as the first frame</param>
        /// <returns>VideoStream object for the new stream</returns>
        public VideoStream AddVideoStream(bool isCompressed, double frameRate, Bitmap firstFrame)
        {
            VideoStream stream = new VideoStream(aviFile, isCompressed, frameRate, firstFrame);
            streams.Add(stream);
            return stream;
        }

        /// <summary>Add a wave audio stream from another file to this file</summary>
        /// <param name="waveFileName">Name of the wave file to add</param>
        /// <param name="startAtFrameIndex">Index of the video frame at which the sound is going to start</param>
        public void AddAudioStream(String waveFileName, int startAtFrameIndex)
        {
            AviManager audioManager = new AviManager(waveFileName, true);
            AudioStream newStream = audioManager.GetWaveStream();
            AddAudioStream(newStream, startAtFrameIndex);
            audioManager.Close();
        }

        private IntPtr InsertSilence(int countSilentSamples, IntPtr waveData, int lengthWave, ref Avi.AVISTREAMINFO streamInfo)
        {
            //initialize silence
            int lengthSilence = countSilentSamples * streamInfo.dwSampleSize;
            byte[] silence = new byte[lengthSilence];

            //initialize new sound
            int lengthNewStream = lengthSilence + lengthWave;
            IntPtr newWaveData = Marshal.AllocHGlobal(lengthNewStream);

            //copy silence
            Marshal.Copy(silence, 0, newWaveData, lengthSilence);

            //copy sound
            byte[] sound = new byte[lengthWave];
            Marshal.Copy(waveData, sound, 0, lengthWave);
            IntPtr startOfSound = new IntPtr(newWaveData.ToInt32() + lengthSilence);
            Marshal.Copy(sound, 0, startOfSound, lengthWave);

            Marshal.FreeHGlobal(newWaveData);

            streamInfo.dwLength = lengthNewStream;
            return newWaveData;
        }

        /// <summary>Add an existing wave audio stream to the file</summary>
        /// <param name="newStream">The stream to add</param>
        /// <param name="startAtFrameIndex">
        /// The index of the video frame at which the sound is going to start.
        /// '0' inserts the sound at the beginning of the video.
        /// </param>
        public void AddAudioStream(AudioStream newStream, int startAtFrameIndex)
        {
            Avi.AVISTREAMINFO streamInfo = new Avi.AVISTREAMINFO();
            Avi.PCMWAVEFORMAT streamFormat = new Avi.PCMWAVEFORMAT();
            int streamLength = 0;

            IntPtr rawData = newStream.GetStreamData(ref streamInfo, ref streamFormat, ref streamLength);
            IntPtr waveData = rawData;

            if (startAtFrameIndex > 0)
            {
                //not supported
                //streamInfo.dwStart = startAtFrameIndex;

                double framesPerSecond = GetVideoStream().FrameRate;
                double samplesPerSecond = newStream.CountSamplesPerSecond;
                double startAtSecond = startAtFrameIndex / framesPerSecond;
                int startAtSample = (int)(samplesPerSecond * startAtSecond);

                waveData = InsertSilence(startAtSample - 1, waveData, streamLength, ref streamInfo);
            }

            IntPtr aviStream;
            int result = Avi.AVIFileCreateStream(aviFile, out aviStream, ref streamInfo);
            if (result != 0)
            {
                throw new Exception("Exception in AVIFileCreateStream: " + result.ToString());
            }

            result = Avi.AVIStreamSetFormat(aviStream, 0, ref streamFormat, Marshal.SizeOf(streamFormat));
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamSetFormat: " + result.ToString());
            }

            result = Avi.AVIStreamWrite(aviStream, 0, streamLength, waveData, streamLength, Avi.AVIIF_KEYFRAME, 0, 0);
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamWrite: " + result.ToString());
            }

            result = Avi.AVIStreamRelease(aviStream);
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamRelease: " + result.ToString());
            }

            Marshal.FreeHGlobal(waveData);
        }

        /// <summary>Add an existing wave audio stream to the file</summary>
        /// <param name="waveData">The new stream's data</param>
        /// <param name="streamInfo">Header info for the new stream</param>
        /// <param name="streamFormat">The new stream' format info</param>
        /// <param name="streamLength">Length of the new stream</param>
        public void AddAudioStream(IntPtr waveData, Avi.AVISTREAMINFO streamInfo, Avi.PCMWAVEFORMAT streamFormat, int streamLength)
        {
            IntPtr aviStream;
            int result = Avi.AVIFileCreateStream(aviFile, out aviStream, ref streamInfo);
            if (result != 0)
            {
                throw new Exception("Exception in AVIFileCreateStream: " + result.ToString());
            }

            result = Avi.AVIStreamSetFormat(aviStream, 0, ref streamFormat, Marshal.SizeOf(streamFormat));
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamSetFormat: " + result.ToString());
            }

            result = Avi.AVIStreamWrite(aviStream, 0, streamLength, waveData, streamLength, Avi.AVIIF_KEYFRAME, 0, 0);
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamWrite: " + result.ToString());
            }

            result = Avi.AVIStreamRelease(aviStream);
            if (result != 0)
            {
                throw new Exception("Exception in AVIStreamRelease: " + result.ToString());
            }
        }

        /// <summary>Copy a piece of video and wave sound int a new file</summary>
        /// <param name="newFileName">File name</param>
        /// <param name="startAtSecond">Start copying at second x</param>
        /// <param name="stopAtSecond">Stop copying at second y</param>
        /// <returns>AviManager for the new video</returns>
        public AviManager CopyTo(String newFileName, float startAtSecond, float stopAtSecond)
        {
            AviManager newFile = new AviManager(newFileName, false);

            try
            {
                //copy video stream

                VideoStream videoStream = GetVideoStream();

                int startFrameIndex = (int)(videoStream.FrameRate * startAtSecond);
                int stopFrameIndex = (int)(videoStream.FrameRate * stopAtSecond);

                videoStream.GetFrameOpen();
                Bitmap bmp = videoStream.GetBitmap(startFrameIndex);
                VideoStream newStream = newFile.AddVideoStream(false, videoStream.FrameRate, bmp);
                for (int n = startFrameIndex + 1; n <= stopFrameIndex; n++)
                {
                    bmp = videoStream.GetBitmap(n);
                    newStream.AddFrame(bmp);
                }
                videoStream.GetFrameClose();

                //copy audio stream

                AudioStream waveStream = GetWaveStream();

                Avi.AVISTREAMINFO streamInfo = new Avi.AVISTREAMINFO();
                Avi.PCMWAVEFORMAT streamFormat = new Avi.PCMWAVEFORMAT();
                int streamLength = 0;
                IntPtr ptrRawData = waveStream.GetStreamData(
                    ref streamInfo,
                    ref streamFormat,
                    ref streamLength);

                int startByteIndex = (int)(startAtSecond * (float)(waveStream.CountSamplesPerSecond * streamFormat.nChannels * waveStream.CountBitsPerSample) / 8);
                int stopByteIndex = (int)(stopAtSecond * (float)(waveStream.CountSamplesPerSecond * streamFormat.nChannels * waveStream.CountBitsPerSample) / 8);

                IntPtr ptrWavePart = new IntPtr(ptrRawData.ToInt32() + startByteIndex);

                byte[] rawData = new byte[stopByteIndex - startByteIndex];
                Marshal.Copy(ptrWavePart, rawData, 0, rawData.Length);
                Marshal.FreeHGlobal(ptrRawData);

                streamInfo.dwLength = rawData.Length;
                streamInfo.dwStart = 0;

                IntPtr unmanagedRawData = Marshal.AllocHGlobal(rawData.Length);
                Marshal.Copy(rawData, 0, unmanagedRawData, rawData.Length);
                newFile.AddAudioStream(unmanagedRawData, streamInfo, streamFormat, rawData.Length);
                Marshal.FreeHGlobal(unmanagedRawData);
            }
            catch (Exception ex)
            {
                newFile.Close();
                throw ex;
            }

            return newFile;
        }

        /// <summary>Release all ressources</summary>
        public void Close()
        {
            foreach (AviStream stream in streams)
            {
                stream.Close();
            }

            Avi.AVIFileRelease(aviFile);
            Avi.AVIFileExit();
        }

        public static void MakeFileFromStream(String fileName, AviStream stream)
        {
            IntPtr newFile = IntPtr.Zero;
            IntPtr streamPointer = stream.StreamPointer;

            Avi.AVICOMPRESSOPTIONS_CLASS opts = new Avi.AVICOMPRESSOPTIONS_CLASS();
            opts.fccType = (uint)Avi.streamtypeVIDEO;
            opts.lpParms = IntPtr.Zero;
            opts.lpFormat = IntPtr.Zero;
            Avi.AVISaveOptions(IntPtr.Zero, Avi.ICMF_CHOOSE_KEYFRAME | Avi.ICMF_CHOOSE_DATARATE, 1, ref streamPointer, ref opts);
            Avi.AVISaveOptionsFree(1, ref opts);

            Avi.AVISaveV(fileName, 0, 0, 1, ref streamPointer, ref opts);
        }
    }

    public class AviPlayer
    {

        private VideoStream videoStream;
        private PictureBox picDisplay;
        private Control ctlFrameIndexFeedback;
        private int millisecondsPerFrame;
        private bool isRunning;
        private int currentFrameIndex;
        private Bitmap currentBitmap;

        private delegate void SimpleDelegate();
        public event EventHandler Stopped;

        /// <summary>Returns the current playback status</summary>
        public bool IsRunning
        {
            get { return isRunning; }
        }

        /// <summary>Create a new AVI Player</summary>
        /// <param name="videoStream">Video stream to play</param>
        /// <param name="picDisplay">PictureBox to display the video</param>
        /// <param name="ctlFrameIndexFeedback">Optional Label to show the current frame index</param>
        public AviPlayer(VideoStream videoStream, PictureBox picDisplay, Control ctlFrameIndexFeedback)
        {
            this.videoStream = videoStream;
            this.picDisplay = picDisplay;
            this.ctlFrameIndexFeedback = ctlFrameIndexFeedback;
            this.isRunning = false;
        }

        /// <summary>Start the video playback</summary>
        public void Start()
        {
            isRunning = true;
            millisecondsPerFrame = (int)(1000 / videoStream.FrameRate);
            Thread thread = new Thread(new ThreadStart(Run));
            thread.Start();
        }

        /// <summary>Extract and display the frames</summary>
        private void Run()
        {
            videoStream.GetFrameOpen();

            for (currentFrameIndex = 0; (currentFrameIndex < videoStream.CountFrames) && isRunning; currentFrameIndex++)
            {
                //show frame
                currentBitmap = videoStream.GetBitmap(currentFrameIndex);
                picDisplay.Invoke(new SimpleDelegate(SetDisplayPicture));
                picDisplay.Invoke(new SimpleDelegate(picDisplay.Refresh));

                //show position
                if (ctlFrameIndexFeedback != null)
                {
                    ctlFrameIndexFeedback.Invoke(new SimpleDelegate(SetLabelText));
                }

                //wait for the next frame
                Thread.Sleep(millisecondsPerFrame);
            }

            videoStream.GetFrameClose();
            isRunning = false;

            if (Stopped != null)
            {
                Stopped(this, EventArgs.Empty);
            }
        }

        /// <summary>Change the visible frame</summary>
        private void SetDisplayPicture()
        {
            picDisplay.Image = currentBitmap;
        }

        /// <summary>Change the frame index feedback</summary>
        private void SetLabelText()
        {
            ctlFrameIndexFeedback.Text = currentFrameIndex.ToString();
        }

        /// <summary>Stop the video playback</summary>
        public void Stop()
        {
            isRunning = false;
        }
    }

    public abstract class AviStream
    {
        protected int aviFile;
        protected IntPtr aviStream;
        protected IntPtr compressedStream;
        protected bool writeCompressed;

        /// <summary>Pointer to the unmanaged AVI file</summary>
        internal int FilePointer
        {
            get { return aviFile; }
        }

        /// <summary>Pointer to the unmanaged AVI Stream</summary>
        internal virtual IntPtr StreamPointer
        {
            get { return aviStream; }
        }

        /// <summary>Flag: The stream is compressed/uncompressed</summary>
        internal bool WriteCompressed
        {
            get { return writeCompressed; }
        }

        /// <summary>Close the stream</summary>
        public virtual void Close()
        {
            if (writeCompressed)
            {
                Avi.AVIStreamRelease(compressedStream);
            }
            Avi.AVIStreamRelease(StreamPointer);
        }

        /// <summary>Export the stream into a new file</summary>
        /// <param name="fileName"></param>
        public abstract void ExportStream(String fileName);

    }

    public class EditableVideoStream : VideoStream
    {
        private IntPtr editableStream = IntPtr.Zero;

        /// <summary>Pointer to the unmanaged AVI stream</summary>
        internal override IntPtr StreamPointer
        {
            get { return editableStream; }
        }

        /// <summary>Create an editable stream from an uneditable stream</summary>
        /// <param name="stream">uneditable stream</param>
        public EditableVideoStream(VideoStream stream)
            : base(stream.FrameSize, stream.FrameRate, stream.Width, stream.Height, stream.CountBitsPerPixel, stream.CountFrames, stream.CompressOptions, stream.WriteCompressed)
        {
            Avi.AVIFileInit();
            int result = Avi.CreateEditableStream(ref editableStream, stream.StreamPointer);

            if (result != 0)
            {
                throw new Exception("Exception in CreateEditableStream: " + result.ToString());
            }

            SetInfo(stream.StreamInfo);
        }

        /// <summary>Close the stream</summary>
        public override void Close()
        {
            base.Close();
            Avi.AVIFileExit();
        }

        /// <summary>Copy a number of frames into a temporary stream</summary>
        /// <param name="start">First frame to copy</param>
        /// <param name="length">Count of frames to copy</param>
        /// <returns>Pointer to the unmanaged temporary stream</returns>
        public IntPtr Copy(int start, int length)
        {
            IntPtr copyPointer = IntPtr.Zero;
            int result = Avi.EditStreamCopy(editableStream, ref start, ref length, ref copyPointer);

            if (result != 0)
            {
                throw new Exception("Exception in Copy: " + result.ToString());
            }

            return copyPointer;
        }

        /// <summary>Move a number of frames into a temporary stream</summary>
        /// <param name="start">First frame to cut</param>
        /// <param name="length">Count of frames to cut</param>
        /// <returns>Pointer to the unmanaged temporary stream</returns>
        public IntPtr Cut(int start, int length)
        {
            IntPtr copyPointer = IntPtr.Zero;
            int result = Avi.EditStreamCut(editableStream, ref start, ref length, ref copyPointer);

            if (result != 0)
            {
                throw new Exception("Exception in Cut: " + result.ToString());
            }

            countFrames -= length;
            return copyPointer;
        }

        /// <summary>Paste a number of frames from another video stream into this stream</summary>
        /// <param name="sourceStream">Stream to copy from</param>
        /// <param name="copyPosition">Index of the first frame to copy</param>
        /// <param name="pastePosition">Where to paste the copied frames</param>
        /// <param name="length">Count of frames to paste</param>
        public void Paste(VideoStream sourceStream, int copyPosition, int pastePosition, int length)
        {
            Paste(sourceStream.StreamPointer, copyPosition, pastePosition, length);
        }

        /// <summary>Paste a number of frames from another video stream into this stream</summary>
        /// <param name="sourceStream">Pointer to the unmanaged stream to copy from</param>
        /// <param name="copyPosition">Index of the first frame to copy</param>
        /// <param name="pastePosition">Where to paste the copied frames</param>
        /// <param name="length">Count of frames to paste</param>
        public void Paste(IntPtr sourceStream, int copyPosition, int pastePosition, int length)
        {
            int pastedLength = 0;
            int result = Avi.EditStreamPaste(editableStream, ref pastePosition, ref pastedLength, sourceStream, copyPosition, length);

            if (result != 0)
            {
                throw new Exception("Exception in Paste: " + result.ToString());
            }

            countFrames += pastedLength;
        }

        /// <summary>Change the AviStreamInfo values and update the frame rate</summary>
        /// <param name="info"></param>
        public void SetInfo(Avi.AVISTREAMINFO info)
        {
            int result = Avi.EditStreamSetInfo(editableStream, ref info, Marshal.SizeOf(info));
            if (result != 0)
            {
                throw new Exception("Exception in SetInfo: " + result.ToString());
            }

            frameRate = info.dwRate / info.dwScale;
        }
    }

    public class VideoStream : AviStream
    {
        /// <summary>handle for AVIStreamGetFrame</summary>
        private int getFrameObject;

        /// <summary>size of an imge in bytes, stride*height</summary>
        private int frameSize;
        public int FrameSize
        {
            get { return frameSize; }
        }

        protected double frameRate;
        public double FrameRate
        {
            get { return frameRate; }
        }

        private int width;
        public int Width
        {
            get { return width; }
        }

        private int height;
        public int Height
        {
            get { return height; }
        }

        private Int16 countBitsPerPixel;
        public Int16 CountBitsPerPixel
        {
            get { return countBitsPerPixel; }
        }

        /// <summary>count of frames in the stream</summary>
        protected int countFrames = 0;
        public int CountFrames
        {
            get { return countFrames; }
        }

        /// <summary>initial frame index</summary>
        /// <remarks>Added by M. Covington</remarks>
        protected int firstFrame = 0;
        public int FirstFrame
        {
            get { return firstFrame; }
        }

        private Avi.AVICOMPRESSOPTIONS compressOptions;
        public Avi.AVICOMPRESSOPTIONS CompressOptions
        {
            get { return compressOptions; }
        }

        public Avi.AVISTREAMINFO StreamInfo
        {
            get { return GetStreamInfo(aviStream); }
        }

        /// <summary>Initialize an empty VideoStream</summary>
        /// <param name="aviFile">The file that contains the stream</param>
        /// <param name="writeCompressed">true: Create a compressed stream before adding frames</param>
        /// <param name="frameRate">Frames per second</param>
        /// <param name="frameSize">Size of one frame in bytes</param>
        /// <param name="width">Width of each image</param>
        /// <param name="height">Height of each image</param>
        /// <param name="format">PixelFormat of the images</param>
        public VideoStream(int aviFile, bool writeCompressed, double frameRate, int frameSize, int width, int height, PixelFormat format)
        {
            this.aviFile = aviFile;
            this.writeCompressed = writeCompressed;
            this.frameRate = frameRate;
            this.frameSize = frameSize;
            this.width = width;
            this.height = height;
            this.countBitsPerPixel = ConvertPixelFormatToBitCount(format);
            this.firstFrame = 0;

            CreateStream();
        }

        /// <summary>Initialize a new VideoStream and add the first frame</summary>
        /// <param name="aviFile">The file that contains the stream</param>
        /// <param name="writeCompressed">true: create a compressed stream before adding frames</param>
        /// <param name="frameRate">Frames per second</param>
        /// <param name="firstFrame">Image to write into the stream as the first frame</param>
        public VideoStream(int aviFile, bool writeCompressed, double frameRate, Bitmap firstFrame)
        {
            Initialize(aviFile, writeCompressed, frameRate, firstFrame);
            CreateStream();
            AddFrame(firstFrame);
        }

        /// <summary>Initialize a new VideoStream and add the first frame</summary>
        /// <param name="aviFile">The file that contains the stream</param>
        /// <param name="writeCompressed">true: create a compressed stream before adding frames</param>
        /// <param name="frameRate">Frames per second</param>
        /// <param name="firstFrame">Image to write into the stream as the first frame</param>
        public VideoStream(int aviFile, Avi.AVICOMPRESSOPTIONS compressOptions, double frameRate, Bitmap firstFrame)
        {
            Initialize(aviFile, true, frameRate, firstFrame);
            CreateStream(compressOptions);
            AddFrame(firstFrame);
        }

        /// <summary>Initialize a VideoStream for an existing stream</summary>
        /// <param name="aviFile">The file that contains the stream</param>
        /// <param name="aviStream">An IAVISTREAM from [aviFile]</param>
        public VideoStream(int aviFile, IntPtr aviStream)
        {
            this.aviFile = aviFile;
            this.aviStream = aviStream;

            Avi.BITMAPINFOHEADER bih = new Avi.BITMAPINFOHEADER();
            int size = Marshal.SizeOf(bih);
            Avi.AVIStreamReadFormat(aviStream, 0, ref bih, ref size);
            Avi.AVISTREAMINFO streamInfo = GetStreamInfo(aviStream);

            this.frameRate = (float)streamInfo.dwRate / (float)streamInfo.dwScale;
            this.width = (int)streamInfo.rcFrame.right;
            this.height = (int)streamInfo.rcFrame.bottom;
            this.frameSize = bih.biSizeImage;
            this.countBitsPerPixel = bih.biBitCount;
            this.firstFrame = Avi.AVIStreamStart(aviStream.ToInt32());
            this.countFrames = Avi.AVIStreamLength(aviStream.ToInt32());
        }

        /// <summary>Copy all properties from one VideoStream to another one</summary>
        /// <remarks>Used by EditableVideoStream</remarks>
        /// <param name="frameSize"></param><param name="frameRate"></param>
        /// <param name="width"></param><param name="height"></param>
        /// <param name="countBitsPerPixel"></param>
        /// <param name="countFrames"></param><param name="compressOptions"></param>
        internal VideoStream(int frameSize, double frameRate, int width, int height, Int16 countBitsPerPixel, int countFrames, Avi.AVICOMPRESSOPTIONS compressOptions, bool writeCompressed)
        {
            this.frameSize = frameSize;
            this.frameRate = frameRate;
            this.width = width;
            this.height = height;
            this.countBitsPerPixel = countBitsPerPixel;
            this.countFrames = countFrames;
            this.compressOptions = compressOptions;
            this.writeCompressed = writeCompressed;
            this.firstFrame = 0;
        }

        /// <summary>Initialize a new VideoStream</summary>
        /// <remarks>Used only by constructors</remarks>
        /// <param name="aviFile">The file that contains the stream</param>
        /// <param name="writeCompressed">true: create a compressed stream before adding frames</param>
        /// <param name="frameRate">Frames per second</param>
        /// <param name="firstFrame">Image to write into the stream as the first frame</param>
        private void Initialize(int aviFile, bool writeCompressed, double frameRate, Bitmap firstFrameBitmap)
        {
            this.aviFile = aviFile;
            this.writeCompressed = writeCompressed;
            this.frameRate = frameRate;
            this.firstFrame = 0;

            BitmapData bmpData = firstFrameBitmap.LockBits(new Rectangle(
                0, 0, firstFrameBitmap.Width, firstFrameBitmap.Height),
                ImageLockMode.ReadOnly, firstFrameBitmap.PixelFormat);

            this.frameSize = bmpData.Stride * bmpData.Height;
            this.width = firstFrameBitmap.Width;
            this.height = firstFrameBitmap.Height;
            this.countBitsPerPixel = ConvertPixelFormatToBitCount(firstFrameBitmap.PixelFormat);

            firstFrameBitmap.UnlockBits(bmpData);
        }

        /// <summary>Get the count of bits per pixel from a PixelFormat value</summary>
        /// <param name="format">One of the PixelFormat members beginning with "Format..." - all others are not supported</param>
        /// <returns>bit count</returns>
        private Int16 ConvertPixelFormatToBitCount(PixelFormat format)
        {
            String formatName = format.ToString();
            if (formatName.Substring(0, 6) != "Format")
            {
                throw new Exception("Unknown pixel format: " + formatName);
            }

            formatName = formatName.Substring(6, 2);
            Int16 bitCount = 0;
            if (Char.IsNumber(formatName[1]))
            {	//16, 32, 48
                bitCount = Int16.Parse(formatName);
            }
            else
            {								//4, 8
                bitCount = Int16.Parse(formatName[0].ToString());
            }

            return bitCount;
        }

        /// <summary>Returns a PixelFormat value for a specific bit count</summary>
        /// <param name="bitCount">count of bits per pixel</param>
        /// <returns>A PixelFormat value for [bitCount]</returns>
        private PixelFormat ConvertBitCountToPixelFormat(int bitCount)
        {
            String formatName;
            if (bitCount > 16)
            {
                formatName = String.Format("Format{0}bppRgb", bitCount);
            }
            else if (bitCount == 16)
            {
                formatName = "Format16bppRgb555";
            }
            else
            { // < 16
                formatName = String.Format("Format{0}bppIndexed", bitCount);
            }

            return (PixelFormat)Enum.Parse(typeof(PixelFormat), formatName);
        }

        private Avi.AVISTREAMINFO GetStreamInfo(IntPtr aviStream)
        {
            Avi.AVISTREAMINFO streamInfo = new Avi.AVISTREAMINFO();
            int result = Avi.AVIStreamInfo(StreamPointer, ref streamInfo, Marshal.SizeOf(streamInfo));
            if (result != 0)
            {
                throw new Exception("Exception in VideoStreamInfo: " + result.ToString());
            }
            return streamInfo;
        }

        private void GetRateAndScale(ref double frameRate, ref int scale)
        {
            scale = 1;
            while (frameRate != (long)frameRate)
            {
                frameRate = frameRate * 10;
                scale *= 10;
            }
        }

        /// <summary>Create a new stream</summary>
        private void CreateStreamWithoutFormat()
        {
            int scale = 1;
            double rate = frameRate;
            GetRateAndScale(ref rate, ref scale);

            Avi.AVISTREAMINFO strhdr = new Avi.AVISTREAMINFO();
            strhdr.fccType = Avi.mmioStringToFOURCC("vids", 0);
            strhdr.fccHandler = Avi.mmioStringToFOURCC("CVID", 0);
            strhdr.dwFlags = 0;
            strhdr.dwCaps = 0;
            strhdr.wPriority = 0;
            strhdr.wLanguage = 0;
            strhdr.dwScale = (int)scale;
            strhdr.dwRate = (int)rate; // Frames per Second
            strhdr.dwStart = 0;
            strhdr.dwLength = 0;
            strhdr.dwInitialFrames = 0;
            strhdr.dwSuggestedBufferSize = frameSize; //height_ * stride_;
            strhdr.dwQuality = -1;        //default
            strhdr.dwSampleSize = 0;
            strhdr.rcFrame.top = 0;
            strhdr.rcFrame.left = 0;
            strhdr.rcFrame.bottom = (uint)height;
            strhdr.rcFrame.right = (uint)width;
            strhdr.dwEditCount = 0;
            strhdr.dwFormatChangeCount = 0;
            strhdr.szName = new UInt16[64];

            int result = Avi.AVIFileCreateStream(aviFile, out aviStream, ref strhdr);

            if (result != 0)
            {
                throw new Exception("Exception in AVIFileCreateStream: " + result.ToString());
            }
        }

        /// <summary>Create a new stream</summary>
        private void CreateStream()
        {
            CreateStreamWithoutFormat();

            if (writeCompressed)
            {
                CreateCompressedStream();
            }
            else
            {
                SetFormat(aviStream);
            }
        }

        /// <summary>Create a new stream</summary>
        private void CreateStream(Avi.AVICOMPRESSOPTIONS options)
        {
            CreateStreamWithoutFormat();
            CreateCompressedStream(options);
        }

        /// <summary>Create a compressed stream from an uncompressed stream</summary>
        private void CreateCompressedStream()
        {
            //display the compression options dialog...
            Avi.AVICOMPRESSOPTIONS_CLASS options = new Avi.AVICOMPRESSOPTIONS_CLASS();
            options.fccType = (uint)Avi.streamtypeVIDEO;

            options.lpParms = IntPtr.Zero;
            options.lpFormat = IntPtr.Zero;
            Avi.AVISaveOptions(IntPtr.Zero, Avi.ICMF_CHOOSE_KEYFRAME | Avi.ICMF_CHOOSE_DATARATE, 1, ref aviStream, ref options);
            Avi.AVISaveOptionsFree(1, ref options);

            //..or set static options
            /*Avi.AVICOMPRESSOPTIONS opts = new Avi.AVICOMPRESSOPTIONS();
            opts.fccType         = (UInt32)Avi.mmioStringToFOURCC("vids", 0);
            opts.fccHandler      = (UInt32)Avi.mmioStringToFOURCC("CVID", 0);
            opts.dwKeyFrameEvery = 0;
            opts.dwQuality       = 0;  // 0 .. 10000
            opts.dwFlags         = 0;  // AVICOMRPESSF_KEYFRAMES = 4
            opts.dwBytesPerSecond= 0;
            opts.lpFormat        = new IntPtr(0);
            opts.cbFormat        = 0;
            opts.lpParms         = new IntPtr(0);
            opts.cbParms         = 0;
            opts.dwInterleaveEvery = 0;*/

            //get the compressed stream
            this.compressOptions = options.ToStruct();
            int result = Avi.AVIMakeCompressedStream(out compressedStream, aviStream, ref compressOptions, 0);
            if (result != 0)
            {
                throw new Exception("Exception in AVIMakeCompressedStream: " + result.ToString());
            }

            SetFormat(compressedStream);
        }

        /// <summary>Create a compressed stream from an uncompressed stream</summary>
        private void CreateCompressedStream(Avi.AVICOMPRESSOPTIONS options)
        {
            int result = Avi.AVIMakeCompressedStream(out compressedStream, aviStream, ref options, 0);
            if (result != 0)
            {
                throw new Exception("Exception in AVIMakeCompressedStream: " + result.ToString());
            }

            this.compressOptions = options;

            SetFormat(compressedStream);
        }

        /// <summary>Add one frame to a new stream</summary>
        /// <param name="bmp"></param>
        /// <remarks>
        /// This works only with uncompressed streams,
        /// and compressed streams that have not been saved yet.
        /// Use DecompressToNewFile to edit saved compressed streams.
        /// </remarks>
        public void AddFrame(Bitmap bmp)
        {
            bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);

            BitmapData bmpDat = bmp.LockBits(
                new Rectangle(
                0, 0, bmp.Width, bmp.Height),
                ImageLockMode.ReadOnly, bmp.PixelFormat);

            int result = Avi.AVIStreamWrite(writeCompressed ? compressedStream : StreamPointer,
                countFrames, 1,
                bmpDat.Scan0,
                (Int32)(bmpDat.Stride * bmpDat.Height),
                0, 0, 0);

            if (result != 0)
            {
                throw new Exception("Exception in VideoStreamWrite: " + result.ToString());
            }

            bmp.UnlockBits(bmpDat);

            countFrames++;
        }

        /// <summary>Apply a format to a new stream</summary>
        /// <param name="aviStream">The IAVISTREAM</param>
        /// <remarks>
        /// The format must be set before the first frame can be written,
        /// and it cannot be changed later.
        /// </remarks>
        private void SetFormat(IntPtr aviStream)
        {
            Avi.BITMAPINFOHEADER bi = new Avi.BITMAPINFOHEADER();
            bi.biSize = Marshal.SizeOf(bi);
            bi.biWidth = width;
            bi.biHeight = height;
            bi.biPlanes = 1;
            bi.biBitCount = countBitsPerPixel;
            bi.biSizeImage = frameSize;

            int result = Avi.AVIStreamSetFormat(aviStream, 0, ref bi, bi.biSize);
            if (result != 0) { throw new Exception("Error in VideoStreamSetFormat: " + result.ToString()); }
        }

        /// <summary>Prepare for decompressing frames</summary>
        /// <remarks>
        /// This method has to be called before GetBitmap and ExportBitmap.
        /// Release ressources with GetFrameClose.
        /// </remarks>
        public void GetFrameOpen()
        {
            Avi.AVISTREAMINFO streamInfo = GetStreamInfo(StreamPointer);

            //Open frames

            Avi.BITMAPINFOHEADER bih = new Avi.BITMAPINFOHEADER();
            bih.biBitCount = countBitsPerPixel;
            bih.biClrImportant = 0;
            bih.biClrUsed = 0;
            bih.biCompression = 0;
            bih.biPlanes = 1;
            bih.biSize = Marshal.SizeOf(bih);
            bih.biXPelsPerMeter = 0;
            bih.biYPelsPerMeter = 0;

            // Corrections by M. Covington:
            // If these are pre-set, interlaced video is not handled correctly.
            // Better to give zeroes and let Windows fill them in.
            bih.biHeight = 0; // was (Int32)streamInfo.rcFrame.bottom;
            bih.biWidth = 0; // was (Int32)streamInfo.rcFrame.right;

            // Corrections by M. Covington:
            // Validate the bit count, because some AVI files give a bit count
            // that is not one of the allowed values in a BitmapInfoHeader.
            // Here 0 means for Windows to figure it out from other information.
            if (bih.biBitCount > 24)
            {
                bih.biBitCount = 32;
            }
            else if (bih.biBitCount > 16)
            {
                bih.biBitCount = 24;
            }
            else if (bih.biBitCount > 8)
            {
                bih.biBitCount = 16;
            }
            else if (bih.biBitCount > 4)
            {
                bih.biBitCount = 8;
            }
            else if (bih.biBitCount > 0)
            {
                bih.biBitCount = 4;
            }

            getFrameObject = Avi.AVIStreamGetFrameOpen(StreamPointer, ref bih);

            if (getFrameObject == 0) { throw new Exception("Exception in VideoStreamGetFrameOpen!"); }
        }

        /// <summary>Export a frame into a bitmap file</summary>
        /// <param name="position">Position of the frame</param>
        /// <param name="dstFileName">Name of the file to store the bitmap</param>
        public void ExportBitmap(int position, String dstFileName)
        {
            Bitmap bmp = GetBitmap(position);
            bmp.Save(dstFileName, ImageFormat.Bmp);
            bmp.Dispose();
        }

        /// <summary>Export a frame into a bitmap</summary>
        /// <param name="position">Position of the frame</param>
        public Bitmap GetBitmap(int position)
        {
            if (position > countFrames)
            {
                throw new Exception("Invalid frame position: " + position);
            }

            Avi.AVISTREAMINFO streamInfo = GetStreamInfo(StreamPointer);

            //Decompress the frame and return a pointer to the DIB
            int dib = Avi.AVIStreamGetFrame(getFrameObject, firstFrame + position);
            //Copy the bitmap header into a managed struct
            Avi.BITMAPINFOHEADER bih = new Avi.BITMAPINFOHEADER();
            bih = (Avi.BITMAPINFOHEADER)Marshal.PtrToStructure(new IntPtr(dib), bih.GetType());

            if (bih.biSizeImage < 1)
            {
                throw new Exception("Exception in VideoStreamGetFrame");
            }

            //copy the image

            byte[] bitmapData;
            int address = dib + Marshal.SizeOf(bih);
            if (bih.biBitCount < 16)
            {
                //copy palette and pixels
                bitmapData = new byte[bih.biSizeImage + Avi.PALETTE_SIZE];
            }
            else
            {
                //copy only pixels
                bitmapData = new byte[bih.biSizeImage];
            }

            Marshal.Copy(new IntPtr(address), bitmapData, 0, bitmapData.Length);

            //copy bitmap info
            byte[] bitmapInfo = new byte[Marshal.SizeOf(bih)];
            IntPtr ptr;
            ptr = Marshal.AllocHGlobal(bitmapInfo.Length);
            Marshal.StructureToPtr(bih, ptr, false);
            address = ptr.ToInt32();
            Marshal.Copy(new IntPtr(address), bitmapInfo, 0, bitmapInfo.Length);

            Marshal.FreeHGlobal(ptr);

            //create file header
            Avi.BITMAPFILEHEADER bfh = new Avi.BITMAPFILEHEADER();
            bfh.bfType = Avi.BMP_MAGIC_COOKIE;
            bfh.bfSize = (Int32)(55 + bih.biSizeImage); //size of file as written to disk
            bfh.bfReserved1 = 0;
            bfh.bfReserved2 = 0;
            bfh.bfOffBits = Marshal.SizeOf(bih) + Marshal.SizeOf(bfh);
            if (bih.biBitCount < 16)
            {
                //There is a palette between header and pixel data
                bfh.bfOffBits += Avi.PALETTE_SIZE;
            }

            //write a bitmap stream
            BinaryWriter bw = new BinaryWriter(new MemoryStream());

            //write header
            bw.Write(bfh.bfType);
            bw.Write(bfh.bfSize);
            bw.Write(bfh.bfReserved1);
            bw.Write(bfh.bfReserved2);
            bw.Write(bfh.bfOffBits);
            //write bitmap info
            bw.Write(bitmapInfo);
            //write bitmap data
            bw.Write(bitmapData);

            Bitmap bmp = (Bitmap)Image.FromStream(bw.BaseStream);
            Bitmap saveableBitmap = new Bitmap(bmp.Width, bmp.Height);
            Graphics g = Graphics.FromImage(saveableBitmap);
            g.DrawImage(bmp, 0, 0);
            g.Dispose();
            bmp.Dispose();

            bw.Close();
            return saveableBitmap;
        }

        /// <summary>Free ressources that have been used by GetFrameOpen</summary>
        public void GetFrameClose()
        {
            if (getFrameObject != 0)
            {
                Avi.AVIStreamGetFrameClose(getFrameObject);
                getFrameObject = 0;
            }
        }

        /// <summary>Copy all frames into a new file</summary>
        /// <param name="fileName">Name of the new file</param>
        /// <param name="recompress">true: Compress the new stream</param>
        /// <returns>AviManager for the new file</returns>
        /// <remarks>Use this method if you want to append frames to an existing, compressed stream</remarks>
        public AviManager DecompressToNewFile(String fileName, bool recompress, out VideoStream newStream2)
        {
            AviManager newFile = new AviManager(fileName, false);

            this.GetFrameOpen();

            Bitmap frame = GetBitmap(0);
            VideoStream newStream = newFile.AddVideoStream(recompress, frameRate, frame);
            frame.Dispose();

            for (int n = 1; n < countFrames; n++)
            {
                frame = GetBitmap(n);
                newStream.AddFrame(frame);
                frame.Dispose();
            }

            this.GetFrameClose();

            newStream2 = newStream;
            return newFile;
        }

        /// <summary>Copy the stream into a new file</summary>
        /// <param name="fileName">Name of the new file</param>
        public override void ExportStream(String fileName)
        {
            Avi.AVICOMPRESSOPTIONS_CLASS opts = new Avi.AVICOMPRESSOPTIONS_CLASS();
            opts.fccType = (uint)Avi.streamtypeVIDEO;
            opts.lpParms = IntPtr.Zero;
            opts.lpFormat = IntPtr.Zero;
            IntPtr streamPointer = StreamPointer;
            Avi.AVISaveOptions(IntPtr.Zero, Avi.ICMF_CHOOSE_KEYFRAME | Avi.ICMF_CHOOSE_DATARATE, 1, ref streamPointer, ref opts);
            Avi.AVISaveOptionsFree(1, ref opts);

            Avi.AVISaveV(fileName, 0, 0, 1, ref aviStream, ref opts);
        }
    }

}
